package com.anasoft.os.m2st.doxia.macro;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.maven.doxia.macro.AbstractMacro;
import org.apache.maven.doxia.macro.Macro;
import org.apache.maven.doxia.macro.MacroExecutionException;
import org.apache.maven.doxia.macro.MacroRequest;

/**
 * Base class for custom {@link Macro} implementations.
 * 
 * @see AbstractMacro
 * @see Macro
 * 
 * @author milan.skuhra
 * @author vojtech.szocs
 */
public abstract class BaseMacro extends AbstractMacro {

    // default source file encoding
    protected static final String DEFAULT_ENCODING = "UTF-8";
    
    // regular expressions for matching start and end of a snippet
    private static final Pattern START_SNIPPET = Pattern.compile("START SNIPPET:\\s*(\\S+)");
    private static final Pattern END_SNIPPET = Pattern.compile("END SNIPPET:\\s*(\\S+)");
    
    /**
     * Returns a {@link URL} for the resource location specified
     * either by <tt>urlParamName</tt> or <tt>fileParamName</tt>
     * {@link MacroRequest} parameters.
     * 
     * <p>
     * 
     * Note that the <tt>urlParamName</tt> parameter always takes
     * precedence over the <tt>fileParamName</tt> when searching
     * for the resource location.
     * 
     * @param request {@link MacroRequest} instance containing
     * appropriate resource location parameter(s).
     * @param urlParamName Parameter name pointing to direct
     * resource {@link URL} as a string value (can be <tt>null</tt>).
     * @param fileParamName Parameter name pointing to absolute
     * or relative {@link File} location (can be <tt>null</tt>).
     * @return Corresponding resource {@link URL}.
     */
    protected URL getResourceUrl(MacroRequest request, String urlParamName, String fileParamName) {
        String url = null;
        if (urlParamName != null)
            url = (String) request.getParameter(urlParamName);
        
        String file = null;
        if (fileParamName != null)
            file = (String) request.getParameter(fileParamName);
        
        if (url != null) {
            try {
                return new URL(url);
            } catch (MalformedURLException e) {
                throw new IllegalArgumentException("Malformed URL " + url);
            }
        }
        
        else if (file != null) {
            File f = new File(file);
            
            if (!f.isAbsolute())
                f = new File(request.getBasedir(), file);
            
            try {
                return f.toURL();
            } catch (MalformedURLException e) {
                throw new IllegalArgumentException("Malformed URL for file " + file);
            }
        }
        
        else {
            throw new IllegalArgumentException("Either '" + urlParamName + "' or '"
                    + fileParamName + "' macro parameter needs to be specified");
        }
    }
    
    /**
     * Returns the value of a macro parameter from the <tt>request</tt>.
     * 
     * <p>
     * 
     * Note that the method throws a {@link MacroExecutionException}
     * when a <tt>required</tt> macro parameter is not found.
     * 
     * @param <T> Expected value type.
     * @param request {@link MacroRequest} instance to check.
     * @param paramName Macro parameter name.
     * @param required <tt>true</tt> to check the presence of the macro
     * parameter within the <tt>request</tt>, <tt>false</tt> otherwise.
     * @return Corresponding macro parameter value.
     * @throws MacroExecutionException
     */
    @SuppressWarnings("unchecked")
    protected <T> T getParameterValue(MacroRequest request, String paramName, boolean required) throws MacroExecutionException {
        T value = (T) request.getParameter(paramName);
        
        if (required && value == null)
            throw new MacroExecutionException("'" + paramName + "' macro parameter needs to be specified");
        
        return value;
    }
    
    /**
     * Loads a snippet from the given <tt>url</tt>.
     * 
     * <p>
     * 
     * This method uses regular expressions to identify the data region
     * within the source file according to the given <tt>id</tt>, for
     * example:
     * 
     * <pre>
     * Some content
     * 
     * START SNIPPET: myCode
     * Content for snippet with id 'myCode'
     * END SNIPPET: myCode
     * 
     * Some content
     * </pre>
     * 
     * @param id Snippet identifier (can be <tt>null</tt>
     * to include the entire source file contents).
     * @param url Source file {@link URL}.
     * @param encoding Source file encoding.
     * @return Snippet contents.
     * @throws IOException
     */
    public static String loadSnippet(String id, URL url, String encoding) throws IOException {
        StringBuilder snippet = new StringBuilder();
        LineIterator lines = IOUtils.lineIterator(url.openStream(), encoding);
        
        try {
            boolean include = (id == null);
            
            while (lines.hasNext()) {
                String line = lines.nextLine();
                
                if (match(line, START_SNIPPET, id)) {
                    include = true;
                } else if (match(line, END_SNIPPET, id)) {
                    break;
                } else if (include == true) {
                    snippet.append(line).append(System.getProperty("line.separator"));
                }
            }
        } finally {
            LineIterator.closeQuietly(lines);
        }
        
        return snippet.toString();
    }
    
    /**
     * Returns <tt>true</tt> when <tt>line</tt> matches
     * the given <tt>pattern</tt> and the first match
     * group is equal to <tt>value</tt>.
     * 
     * <p>
     * 
     * Returns <tt>false</tt> when the <tt>value</tt>
     * is <tt>null</tt>.
     * 
     * @param line Line input to match.
     * @param pattern Pattern to match against.
     * @param value Expected first match group value.
     * @return Input match result.
     */
    private static boolean match(String line, Pattern pattern, String value) {
        if (value == null)
            return false;
        
        Matcher m = pattern.matcher(line);
        return m.find() && value.equals(m.group(1));
    }
    
}
